ElastiCache学習の第一歩に、Webアプリケーションのセッション情報を保存する公式チュートリアルをやってみた
コンサル部@大阪オフィスのYui(@MayForBlue)です。
最近初めてElastiCacheを触る機会があり、最初の一歩として公式のチュートリアルをやってみました。
より構築しやすいように手順を変えてみたり、構築後に簡単なテストをするなど少し工夫してやってみたので、手順をご紹介したいと思います。
ElastiCache とは
ElastiCache とは、インメモリデータストアやインメモリキャッシュとして機能するAWSのフルマネージドサービスです。
詳細は以下のブログをご参照ください。
チュートリアル内容
リンク
Amazon ElastiCache for Redis を使い、オンラインアプリケーション用の高速セッションストアを構築する
概要
PythonのマイクロフレームワークFlaskで簡易Webアプリケーションを作成し、セッション情報をElastiCache(Redis)に保存する
構成図
構成は以下のようになります。
プライベートサブネットにWebアプリケーション用のEC2を用意し、プライベートサブネットにElastiCacheのプライマリ/レプリカノードを配置します。
前準備
チュートリアルに使用するVPC、サブネット、インターネットゲートウェイはCloudFormationで構築しました。
テンプレートはこちら
AWSTemplateFormatVersion: "2010-09-09" Description: VPC and Subnet Create Metadata: "AWS::CloudFormation::Interface": ParameterGroups: - Label: default: "Project Name Prefix" Parameters: - ProjectName - Label: default: "Network Configuration" Parameters: - VPCCIDR - PublicSubnetACIDR - PublicSubnetCCIDR - PrivateSubnetACIDR - PrivateSubnetCCIDR ParameterLabels: ProjectName: defalut: "ProjectName" VPCCIDR: default: "VPC CIDR" PublicSubnetACIDR: default: "PublicSubnetA CIDR" PublicSubnetCCIDR: default: "PublicSubnetC CIDR" PrivateSubnetACIDR: default: "PrivateSubnetA CIDR" PrivateSubnetCCIDR: default: "PrivateSubnetC CIDR" Parameters: ProjectName: Type: String Default: "elc-tutorial" VPCCIDR: Type: String Default: "10.1.0.0/16" PublicSubnetACIDR: Type: String Default: "10.1.10.0/24" PublicSubnetCCIDR: Type: String Default: "10.1.20.0/24" PrivateSubnetACIDR: Type: String Default: "10.1.100.0/24" PrivateSubnetCCIDR: Type: String Default: "10.1.200.0/24" Resources: # ------------------------------------------------------------# # VPC # ------------------------------------------------------------# # VPC VPC: Type: "AWS::EC2::VPC" Properties: CidrBlock: !Ref VPCCIDR EnableDnsSupport: true EnableDnsHostnames: true InstanceTenancy: default Tags: - Key: Name Value: !Sub "${ProjectName}-vpc" # InternetGateway InternetGateway: Type: "AWS::EC2::InternetGateway" Properties: Tags: - Key: Name Value: !Sub "${ProjectName}-igw" InternetGatewayAttachment: Type: "AWS::EC2::VPCGatewayAttachment" Properties: InternetGatewayId: !Ref InternetGateway VpcId: !Ref VPC # ------------------------------------------------------------# # Subnet # ------------------------------------------------------------# # Public SubnetA PublicSubnetA: Type: "AWS::EC2::Subnet" Properties: AvailabilityZone: "ap-northeast-1a" CidrBlock: !Ref PublicSubnetACIDR VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectName}-public-subnet-a" # Public SubnetC PublicSubnetC: Type: "AWS::EC2::Subnet" Properties: AvailabilityZone: "ap-northeast-1c" CidrBlock: !Ref PublicSubnetCCIDR VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectName}-public-subnet-c" # Private SubnetA PrivateSubnetA: Type: "AWS::EC2::Subnet" Properties: AvailabilityZone: "ap-northeast-1a" CidrBlock: !Ref PrivateSubnetACIDR VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectName}-private-subnet-a" # Private SubnetC PrivateSubnetC: Type: "AWS::EC2::Subnet" Properties: AvailabilityZone: "ap-northeast-1c" CidrBlock: !Ref PrivateSubnetCCIDR VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectName}-private-subnet-c" # Public RouteTableA PublicRouteTableA: Type: "AWS::EC2::RouteTable" Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectName}-public-route-a" # Public RouteTableC PublicRouteTableC: Type: "AWS::EC2::RouteTable" Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectName}-public-route-c" # Private RouteTableA PrivateRouteTableA: Type: "AWS::EC2::RouteTable" Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectName}-private-route-a" # Private RouteTableC PrivateRouteTableC: Type: "AWS::EC2::RouteTable" Properties: VpcId: !Ref VPC Tags: - Key: Name Value: !Sub "${ProjectName}-private-route-c" # Routing # PublicRouteA PublicRouteA: Type: "AWS::EC2::Route" Properties: RouteTableId: !Ref PublicRouteTableA DestinationCidrBlock: "0.0.0.0/0" GatewayId: !Ref InternetGateway # PublicRouteC PublicRouteC: Type: "AWS::EC2::Route" Properties: RouteTableId: !Ref PublicRouteTableC DestinationCidrBlock: "0.0.0.0/0" GatewayId: !Ref InternetGateway # RouteTable Associate # PublicRouteTableA PublicSubnetARouteTableAssociation: Type: "AWS::EC2::SubnetRouteTableAssociation" Properties: SubnetId: !Ref PublicSubnetA RouteTableId: !Ref PublicRouteTableA # PublicRouteTableC PublicSubnetCRouteTableAssociation: Type: "AWS::EC2::SubnetRouteTableAssociation" Properties: SubnetId: !Ref PublicSubnetC RouteTableId: !Ref PublicRouteTableC # PrivateRouteTableA PrivateSubnetARouteTableAssociation: Type: "AWS::EC2::SubnetRouteTableAssociation" Properties: SubnetId: !Ref PrivateSubnetA RouteTableId: !Ref PrivateRouteTableA # PrivateRouteTableC PrivateSubnetCRouteTableAssociation: Type: "AWS::EC2::SubnetRouteTableAssociation" Properties: SubnetId: !Ref PrivateSubnetC RouteTableId: !Ref PrivateRouteTableC
やってみる
サーバーの構築
まずはアプリケーションサーバーとなるEC2を構築します。
EC2のダッシュボードに移動し、「インスタンスの作成」をクリックします。
Amazon Linux 2 のAMIを選択します。
インスタンスタイプはt2.microを選択します。
インスタンス詳細の設定で以下を設定し、その他の値はデフォルトで「ストレージの追加」をクリックします。
1. ネットワーク:チュートリアル用に作成したものを選択(elc-tutorial-vpc
)
2. サブネット: elc-tutorial-public-subnet-a
3. 自動割り当てパブリックIP:有効
ストレージサイズはデフォルトのまま「タグの追加」をクリックします。
任意のタグを設定します。ここでは以下を設定しました。
Name:ecl-tutorial-server
セキュリティグループを設定します。
「新しいセキュリティグループを作成する」にチェックを入れ、以下を設定しました。
- セキュリティグループ名:
ecl-tutorial-server-sg
- 説明:
ecl-tutorial-server-sg
- インバウンドルール
タイプ | プロトコル | ポート範囲 | ソース | 説明 |
---|---|---|---|---|
SSH | TCP | 22 | マイIP | from my IP |
カスタムTCP | TCP | 5000 | 0.0.0.0/0 | from All |
インバウンドルールの1つ目はSSHログイン用、2つ目はWebアプリケーションへのアクセス用です。
内容を確認して問題なければ「起動」をクリックします。
ログイン用のキーペアを選択して「インスタンスの作成」をクリックします。
キーペアが未作成の場合は「新しいキーペアを作成する」を選択してください。
ElastiCacheの構築
次に、ElastiCacheを構築していきます。
インスタンスを構築する前に、構築に必要なセキュリティグループとサブネットグループを作成します。
まずはセキュリティグループからです。
EC2のダッシュボードの左側ペインより「セキュリティグループ」を選択し、「セキュリティグループの作成」をクリックします。
以下を設定し、「セキュリティグループの作成」をクリックします。
- セキュリティグループ名:
ecl-tutorial-redis-sg
- 説明:
ecl-tutorial-redis-sg
- インバウンドルール
タイプ | プロトコル | ポート範囲 | ソース | 説明 |
---|---|---|---|---|
カスタムTCP | TCP | 6379 | ecl-tutorial-server-sg | from server |
インバウンドルールでアプリケーションサーバーからポート番号6379の通信を許可する設定を入れています。
次にサブネットグループを作成します。
ElastiCacheのダッシュボードの左側ペインから「サブネットグループ」を選択します。
「サブネットグループの作成」をクリックします。
以下を設定し、「作成」をクリックします。
- 名前:
elc-tutorial-redis-subnet-group
- 説明:
elc-tutorial-redis-subnet-group
- VPC:
elc-tutorial-vpc
- サブネット
- elc-tutorial-private-subnet-a / ap-northeast-1a
- elc-tutorial-private-subnet-c / ap-northeast-1c
いよいよElastiCacheを構築していきます。
ElastiCacheのダッシュボードから「今すぐ始める」をクリックします。
以下を設定します。(その他の値はデフォルトのままで大丈夫です。)
- クラスターエンジン:Redis
- 名前:
elc-tutorial-redis
- 説明:
elc-tutorial-redis
- ノードのタイプ:
cache.t2.micro
- レプリケーション数:
1
- サブネットグループ:
elc-tutorial-redis-subnet-group
- セキュリティグループ:
ecl-tutorial-redis-sg
- 自動バックアップの有効化:無効
しばらくしてステータスがavailableになれば構築完了です。
アプリケーションの設定をする
サーバーにログインし、Webアプリケーションとしての設定、ElastiCacheとの接続を行います。
EC2にログインします。
$ ssh -i <key-pair>.pem <ユーザ名>@<パブリックIP or パブリックDNS名>
ログイン方法の詳細についてはこちらのブログをご参照ください。
ログインできたら、以下のコマンドを順番に実行します。
$ sudo yum install tmux $ sudo amazon-linux-extras install redis4.0 $ sudo yum install git $ sudo yum install python3 $ sudo pip3 install flask $ sudo pip3 install virtualenv $ git clone https://github.com/aws-samples/amazon-elasticache-samples/ $ cd amazon-elasticache-samples/session-store $ virtualenv venv $ source ./venv/bin/activate $ pip3 install -r requirements.txt $ export FLASK_APP=example-1.py $ export SECRET_KEY=some_secret_string $ flask run -h 0.0.0.0 -p 5000 --reload
チュートリアルに記載されていたものに加え、Flask、テストに使用するRedisクライアントツール、tmuxをインストールしています。
Flaskの起動が成功したら、ブラウザからアクセス確認してみます。
以下のようにhttp://【パブリックDNS】:5000/
にアクセスできれば成功です。
ElastiCache(Redis)と接続する
amazon-elasticache-samples/session-store/example-1.py
を編集し、既存のコードを削除して以下のコードを貼り付けます。
(コードの内容についてはチュートリアル内で解説されているため、ここでは割愛します)
チュートリアル内ではセッション情報保持期間が10秒間に設定されていますが、ここではテストのために180秒間に設定しました。
import os import redis from flask import Flask, session, redirect, escape, request app = Flask(__name__) app.secret_key = os.environ.get('SECRET_KEY', default=None) # BEGIN NEW CODE - PART 1 # REDIS_URL = os.environ.get('REDIS_URL') store = redis.Redis.from_url(REDIS_URL) # END NEW CODE - PART 1 # @app.route('/') def index(): if 'username' in session: # BEGIN NEW CODE - PART 2 # username = escape(session['username']) visits = store.hincrby(username, 'visits', 1) # BEGIN NEW CODE - EXPIRE SESSIONS # store.expire(username, 180) # END NEW CODE - EXPIRE SESSIONS # return ''' Logged in as {0}.<br> Visits: {1} '''.format(username, visits) # END NEW CODE - PART 2 # return 'You are not logged in' @app.route('/login', methods=['GET', 'POST']) def login(): if request.method == 'POST': session['username'] = request.form['username'] return redirect('/') return ''' <form method="post"> <p><input type=text name=username> <p><input type=submit value=Login> </form>''' @app.route('/logout') def logout(): session.pop('username', None) return redirect('/')
次に、環境変数にRedisのプライマリエンドポイントを設定します。
$ export REDIS_URL="redis://【プライマリエンドポイント】:6379"
設定できたら、tmuxを起動します。
$ tmux
一つ目のウィンドウで以下のコマンドを実行し、Redisに値が保存されていないことを確認します。
$redis-cli -h 【プライマリエンドポイント】 elc-tutorial-redis.xxxx.ng.0001.apne1.cache.amazonaws.com:6379> keys * (empty list or set)
Ctrl+b
→c
をクリックし、新規ウィンドウを作成します。
以下のコードを実行し、Flaskサーバーを起動します。
$ flask run -h 0.0.0.0 -p 5000 --reload * Serving Flask app "example-1.py" (lazy loading) * Environment: production WARNING: This is a development server. Do not use it in a production deployment. Use a production WSGI server instead. * Debug mode: off * Running on http://0.0.0.0:5000/ (Press CTRL+C to quit) * Restarting with stat
起動できたら、http://【パブリックDNS】:5000/login
にアクセスし、以下のような画面が表示されていることを確認します。
任意の文字列を入力してログインすると、Redisに値が保存されます。
a
という値で2回、b
という値で2回ログインしてみました。
tmuxの一つ目のウィンドウで値が保存されていることを確認してみました。
今回はアプリケーション側でハッシュ値を保存しているため、値参照のコマンドにはhgetall
を使用しています。
elc-tutorial-redis.xxxx.ng.0001.apne1.cache.amazonaws.com:6379>keys * 1) "b" 2) "a" elc-tutorial-redis.xxxx.ng.0001.apne1.cache.amazonaws.com:6379> hgetall a 1) "visits" 2) "2" elc-tutorial-redis.xxxx.ng.0001.apne1.cache.amazonaws.com:6379> hgetall b 1) "visits" 2) "1"
期待した値が保存されていることが確認できました!
後片付け
最後に、今回構築した以下を削除してチュートリアル完了です。
- ElastiCache
- Redisインスタンス
- サブネットグループ
- セキュリティグループ
- EC2
- EC2インスタンス
- セキュリティグループ
- ネットワーク
- CloudFormationテンプレート
最後に
ElastiCacheについて概要は知っていたものの、やはり実際に手を動かして作ってみるのが大事だなと思いました。
今回のチュートリアルでは構築に加え、簡易的なものではありますが、アプリケーションに組み込む方法も知れたのが良かったです。
また、今回のチュートリアルで学べるのは最初の構築の部分のみなので、ElastiCacheの詳細な挙動や性質については別途勉強していきたいと思います。
この記事がどなたかのお役に立てば幸いです。
以上、大阪オフィスのYui(@MayForBlue)でしたっ(`・ω・´)